#!/bin/bash
# Copyright (c) 2023 - 2026 DisplayLink (UK) Ltd.

are_dl_processes_running()
{
    pgrep DisplayLink[^XpcService] &> /dev/null
}

get_users()
{
  dscl . list /Users | grep -v -e '^_.*' -e daemon -e nobody
}

transfer_legacy_autostart_settings()
{
  echo -e "\n> Transfering legacy autostart settings"

  local user=$1
  local dlua_xml
  dlua_xml="/Users/$user/Library/Group Containers/73YQY62QM3.com.displaylink.DisplayLinkShared/Library/Application Support/Logs/.dlua.xml"

  local autostart
  autostart=$(grep '<value name="AutoStart">' "$dlua_xml" 2> /dev/null | grep -o 'yes\|no') #read AutoStart value from dlua.xml
  [ -z "$autostart" ] && return
  sed -i '' 's/\<value name="AutoStart"\>[a-z]\{2,3\}\<\/value\>//g' "$dlua_xml" 2> /dev/null #delete AutoStart key from dlua.xml

  autostart=$(echo "$autostart" | sed 's/yes/1/g;s/no/0/g') #replace "yes" with 1 and "no" with 0 for coherence in defaults
  su -l "$user" -c "defaults write /Users/$user/Library/Containers/com.displaylink.DisplayLinkUserAgent/Data/Library/Preferences/com.displaylink.DisplayLinkUserAgent.plist AppAutostart $autostart" #transfer legacy value
}

set_default_autostart_value()
{
  echo -e "\n> Setting default autostart value"

  local user=`stat -f%Su /dev/console`
  su -l "$user" -c "defaults read com.displaylink.DisplayLinkUserAgent AppAutostart 2>&1 | grep \"does not exist\""
  if [[ "$?" -eq 0 ]]; then
    echo "Enabling autostart by default"
    su -l "$user" -c "defaults write com.displaylink.DisplayLinkUserAgent AppAutostart -bool YES"
  else
    echo "Autostart value already set"
  fi
}

remove_legacy_displaylink_useragents_and_daemons_for_all_users()
{
  echo -e "\n> Removing legacy DisplayLink user agents"

  for user in $(get_users); do
    su -l "$user" -c 'launchctl remove com.displaylink.useragent' # legacy useragent name
    su -l "$user" -c 'launchctl remove com.displaylink.DisplayLink Manager' # legacy useragent name
    su -l "$user" -c 'launchctl remove com.displaylink.DisplayLinkManager' # when running as appstore app this is user daemon
    su -l "$user" -c 'launchctl remove com.displaylink.DisplayLinkLoginHelper' # when running as appstore app this is login helper
  done

  echo -e "\n> Removing DisplayLink daemon"
  launchctl remove com.displaylink.displaylinkmanager # legacy daemon name
  launchctl remove com.displaylink.DisplayLinkManager
}

remove_legacy_displaylink_software()
{
  echo -e "\n> Removing legacy DisplayLink software"

  files=(
    "/Applications/DisplayLink"
    "/Library/Application Support/DisplayLink"
    "/Library/LaunchAgents/73YQY62QM3.com.displaylink.DisplayLinkAPServer.plist"
    "/Library/LaunchAgents/com.displaylink.useragent-prelogin.plist"
    "/Library/LaunchAgents/com.displaylink.useragent.plist"
    "/Library/LaunchDaemons/com.displaylink.displaylinkmanager.plist"
    "/Library/LaunchDaemons/com.displaylink.launchd.plist"
    "/Library/Preferences/com.displaylink.plist"
    "/Library/PrivilegedHelperTools/DisplayLink"
    "/Library/Receipts/DisplayLink Software Installer.pkg"
    "/Library/StartupItems/DisplayLinkAgent"
    "/System/Library/Extensions/DisplayLinkGA.plugin"
    "/System/Library/Extensions/DisplayLinkGA_10.6.plugin"
    "/etc/asl/com.displaylink.windowserver"
    "/var/db/receipts/com.displaylink*"
    "/var/log/displaylink"
  )

  for f in "${files[@]}"
  do
    if [[ -e $f || -h $f ]]
    then
      echo "Removing $f..."
      if ! rm -R "$f"
      then
        echo "ERROR: Unable to remove $f. Please remove manually."
      fi
      echo "$f successfully removed."
    else
      echo "$f not found, skipping."
    fi
  done
}

remove_current_displaylink_software()
{
  echo -e "\n> Removing DisplayLink software"

  files=(
    "/Applications/DisplayLink Software Uninstaller.app"
    "/Applications/DisplayLink Manager.app"
  )

  for f in "${files[@]}"
  do
    if [[ -e $f || -h $f ]]
    then
      echo "Removing $f..."
      if ! rm -R "$f"
      then
        echo "ERROR: Unable to remove $f. Please remove manually."
      fi
      echo "$f successfully removed."
    else
      echo "$f not found, skipping."
    fi
  done
}

remove_login_screen_extension()
{
  echo -e "\n> Removing login screen extension"
  files=(
    "/Library/LaunchAgents/com.displaylink.loginscreen.plist"
  )

  for f in "${files[@]}"
  do
    if [[ -e $f || -h $f ]]
    then
      echo "Removing $f..."
      if ! rm -R "$f"
      then
        echo "ERROR: Unable to remove $f. Please remove manually."
      fi
      echo "$f successfully removed."
    else
      echo "$f not found, skipping."
    fi
  done
}

remove_current_displaylink_useragents_and_daemons_for_all_users()
{
  echo -e "\n> Removing DisplayLink user agents"
  for user in $(get_users); do
    su -l "$user" -c 'launchctl remove com.displaylink.loginscreen' # pre-login agent for appstore app
  done
}

remove_application_scripts()
{
  echo -e "\n> Remove application scripts"
  for user in ~root /Users/*
  do
    rm -fr "$user/Library/Application Scripts/com.displaylink.DisplayLinkLoginHelper"
    rm -fr "$user/Library/Application Scripts/com.displaylink.DisplayLinkManager"
    rm -fr "$user/Library/Application Scripts/com.displaylink.DisplayLinkUserAgent"
    rm -fr "$user/Library/Application Scripts/com.displaylink.DisplayLink_Manager"
    rm -fr "$user/Library/Application Scripts/73YQY62QM3.com.displaylink.DisplayLinkShared"
  done
}

remove_application_preferences()
{
  echo -e "\n> Remove application preferences"
  for user in $(get_users); do
    su -l "$user" -c 'defaults delete com.displaylink.DisplayLinkUserAgent'
  done

  for user in ~root /Users/*
  do
    rm -fr "$i/Library/Preferences/73YQY62QM3.com.displaylink.DisplayLinkShared.plist"
  done
}

terminate_any_remaining_dl_processes()
{
  if are_dl_processes_running; then
    echo "Terminate remaining DisplayLink processes"
    killall -q -SIGTERM 'DisplayLink Manager'
    killall -q -SIGTERM DisplayLinkUserAgent
    killall -q -SIGTERM DisplayLinkManager
  fi

  wait_for_dl_processes_finished 5
  if are_dl_processes_running; then
    echo "Kill remaining DisplayLink processes"
    killall -q -SIGKILL 'DisplayLink Manager'
    killall -q -SIGKILL DisplayLinkUserAgent
    killall -q -SIGKILL DisplayLinkManager
  fi
}

terminate_crash_restart_helper()
{
  echo "Terminate CrashRestartHelper"
  killall -q -SIGTERM CrashRestartHelper
}

terminate_xpc_service()
{
  echo "Terminate DisplayLinkXpcService"
  killall -q -SIGTERM DisplayLinkXpcService
}

wait_for_dl_processes_finished()
{
  local timeout=$1

  while [[ $(( timeout-- )) -gt 0 ]]
  do
    if ! are_dl_processes_running; then
      echo "All DisplayLink processes are finished"
      break
    fi

    echo "Wait for dl prcesses to finish"
    sleep 1
  done
}

rebuildKextCache()
{
  echo -e "\n> Refresh kext cache"
  touch /System/Library/Extensions
  touch /Library/Extensions
  rm -f /System/Library/Extensions.kextcache
}

removeKEXTs()
{
  echo -e "\n> Removing KEXT driver"

  local kext_uninstall_flag=/tmp/dl_app_installer_requires_restart

  local kextfiles=(
    "/Library/Extensions/DisplayLinkDriver.kext"
    "/System/Library/Extensions/DisplayLinkDriver.kext"
    "/Library/Extensions/DisplayLinkEthernetDriver.kext"
    "/System/Library/Extensions/DisplayLinkDriver0.9.kext"
    "/System/Library/Extensions/DisplayLinkEthernetDriver.kext"
    "/System/Library/Extensions/DisplayLinkNcmDriver.kext"
    "/System/Library/Extensions/DisplayLinkNullDriver.kext"
  )

  for f in "${kextfiles[@]}"
  do
    if [[ -e $f || -h $f ]]
    then
      echo "Removing $f..."
      if ! rm -R "$f"
      then
        echo "ERROR: Unable to remove $f. Please remove manually."
      fi
      touch "$kext_uninstall_flag"
      echo "$f KEXT successfully removed."
    else
      echo "$f KEXT not found, skipping."
    fi
  done

  if [[ -e $kext_uninstall_flag ]]
  then
    rebuildKextCache
  fi
}

remove_plist_from_user_directories()
{
  echo -e "\n> Remove plists from user directories"
  for i in ~root /Users/*
  do
    rm -vf "$i/Library/Preferences/com.displaylink.displaylinkmanager.plist"
    rm -vf "$i/Library/Preferences/com.displaylink.airdisplaypriority.plist"
  done
}

echo "DisplayLink Cleaner script"

# If incorrect number of arguments is given, let's do "partial" action
# macOS Installer gives us 5 (unwanted) arguments
# https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/SoftwareDistribution4/Install_Operations/Install_Operations.html#//apple_ref/doc/uid/TP40004615-CH14-SW1
if [[ $# -eq 1 ]]
then
  readonly things_to_clean=$1
else
  readonly things_to_clean="partial"
fi

echo -e "\n> DisplayLink Cleaner is going to clean ${things_to_clean}"

if [[ "$things_to_clean" =~ (^|,)partial($|,) ]];
then
  launchctl list | grep DisplayLink &> /dev/null
  readonly had_deamons_installed=$?

  remove_legacy_displaylink_useragents_and_daemons_for_all_users
  transfer_legacy_autostart_settings
  set_default_autostart_value

  if [[ $had_deamons_installed -eq 0 ]]; then
    wait_for_dl_processes_finished 10
  fi

  terminate_crash_restart_helper
  terminate_any_remaining_dl_processes
  remove_legacy_displaylink_software
  removeKEXTs
  remove_plist_from_user_directories
fi

if [[ "$things_to_clean" =~ (^|,)dlm($|,) ]];
then
  launchctl list | grep DisplayLink &> /dev/null
  readonly had_deamons_installed=$?

  remove_legacy_displaylink_useragents_and_daemons_for_all_users
  remove_current_displaylink_useragents_and_daemons_for_all_users

  if [[ $had_deamons_installed -eq 0 ]]; then
    wait_for_dl_processes_finished 10
  fi

  terminate_crash_restart_helper
  terminate_any_remaining_dl_processes
  remove_legacy_displaylink_software
  remove_current_displaylink_software
  terminate_xpc_service
  removeKEXTs
  remove_plist_from_user_directories
  remove_application_scripts
  remove_application_preferences
fi

if [[ "$things_to_clean" =~ (^|,)lse($|,) ]];
then
  remove_login_screen_extension
fi

echo -e "\n> DisplayLink uninstall script done."
